from datetime import date, datetime, timedelta
from collections import namedtuple
import re
import httplib2
import apiclient.discovery
import oauth2client.client

class Event(object):
	def __init__(self, name):
		self.name = name
	@property
	def date(self):
		return self._start.date()
	@date.setter
	def date(self, value):
		duration = self.duration
		self._start = datetime.combine(value, self._start.timetz())
#		self._stop = datetime.combine(value, self._stop.timetz())
		self.duration = duration
	@property
	def start_time(self):
		return self._start.timetz()
	@start_time.setter
	def start_time(self, value):
		duration = self.duration
		self._start = datetime.combine(self._start, value)
		self.duration = duration
	@property
	def stop_time(self):
		return self._stop.timetz()
	@stop_time.setter
	def stop_time(self, value):
		self._stop = datetime.combine(self._stop, value)
	@property
	def duration(self):
		return self._stop - self._start
	@duration.setter
	def duration(self, value):
		self._stop = self._start + value

User = namedtuple('User', 'name email')

Recurrence = namedtuple('Recurrence', 'days time begin end')

class Schedule(object):

	def __init__(self, config):
		self.config = config
		self.update = datetime.min

	def connect(self, showurlfn):
		storage = CryptStorage(foo)
		credent = storage.get()
		if credent.invalid:
			scope = 'https://www.googleapis.com/auth/calendar'
			flow = oauth2client.client.OAuth2WebServerFlow(
										client_id=credent.client_id, 
										client_secret=credent.client_secret,
										scope=scope, 
										user_agent=credent.user_agent)
			authurl = flow.step1_get_authorize_url()
			authcode = showurlfn(authurl)
			credent = flow.step2_exchange(authcode)
			storage.put(credent)
		http = httplib2.Http()
		http = credent.authorize(http)
		calapi = apiclient.discovery.build('calendar', 'v3', http)
		callst = calapi.calendarList().list(showHidden=True, 
								fields='items(id,summary,location)').execute()
		for cal in callst.get('items', ()):
			if (cal.get('location') == self.config.campus and 
					cal.get('summary') == self.config.room):
				self.calid = cal['id']
				break
		else:
			raise foo
		self.evtsvc = calapi.events()

	def updated(self):
		now = datetime.now()
		if now >= self.next_update:
			return True
		evtlst = self.evtsvc.list(calendarId=self.calid, maxResults=1, 
								  fields='updated').execute()
		gcal_update = datetime.parse(evtlst, 'updated')
		if gcal_update > self.last_update:
			return True
		return False

	def fetch(self):
		now = datetime.now()
		timeMin = format_datetime(now)
		timeMax = format_datetime(now + self.config.fetch_span)
		evtlst = self.evtsvc.list(calendarId=self.calid, fields='items(id,recurrence,start)',
								  timeMin=timeMin, timeMax=timeMax).execute()
		recurmap = {evt['id']: parse_recurrence(evt)
							for evt in evtlst.get('items', ())}
		#
		evtlst = self.evtsvc.list(calendarId=self.calid, fields=self.EVENT_FIELDS,
								  singleEvents=True, orderBy='startTime',
								  timeMin=timeMin, timeMax=timeMax).execute()
		self.sessions = [parse_event(evt, recurmap)
							for evt in evtlst.get('items', ())]
		#
		self.last_update = now
		self.next_update = now + (self.config.fetch_span / 2)

EVENT_FIELDS = ('items(summary,id,start,end,organizer/displayName,htmlLink,'
					  'attendees(email,displayName),description,recurringEventId)')

def parse_recur(evt):
	if 'recurrence' not in evt:
		return None
#	rdata = {'RRULE': [], 'EXRULE': [], 'RDATE': [], 'EXDATE': []}
	rulemap = {}
	for rule in evt['recurrence']:
		key, sep, val = rule.upper().partition(':')
		if not sep:
			continue
		key = key.split(';')
		tag = key[0]
		if tag in ('RRULE', 'EXRULE'):
			val = dict(item.split('=', 1) for item in val.split(';'))
#			rdata[tag].append((par, val))
		elif tag in ('RDATE', 'EXDATE'):
			val = val.split(',')
#			rdata[tag].append((par, val))
		else:
			continue
		par = dict(item.split('=', 1) for item in key[1:])
		# i think it's unlikely that there will be more than one instance of each rule type
#		rulemap.setdefault(tag, []).append((par, val))
		rulemap[tag] = (par, val)
	start = parse_datetime(evt['start'])
	days = 'irreg'
	time = start.time().strftime('%H%M')
	begin = start.date()
	end = None
#	rrule = rdata['RRULE']
	if 'RRULE' in rulemap:
		par, val = rulemap['RRULE']
#		par, val = rrule[0]
		freq = val['FREQ']
		if freq == 'WEEKLY':
			if 'BYDAY' in val:
				daymap = {'MO':'M', 'TU':'T', 'WE':'W', 'TH':'R', 'FR':'F', 'SA':'S', 'SU':'U'}
				days = ''.join(daymap[day[-2:]] for day in val['BYDAY'].split(','))
			else:
				days = 'MTWRFSU'[start.weekday()]
		elif freq == 'DAILY':
			days = val.get('INTERVAL', '1') + 'd'
		elif freq == 'MONTHLY':
			# XXX todo: need to differentiate between 'day of the month' and 'day of the week'
			pass
		else:
			# XXX todo: probably not worth it to check for yearly or subdaily recurrences,
			#           but what to indicate instead?
			pass
		if 'UNTIL' in val:
			# XXX fixme: i think that since the UNTIL datetime is in UTC, we
			#            have to convert it to the same timezone as start
#			format = '%Y%m%dT%H%M%SZ' if val['UNTIL'][8:9] == 'T' else '%Y%m%d'
			end = datetime.strptime(val['UNTIL'][:8], '%Y%m%d').date()
		elif 'COUNT' in val:
			end = begin + timedelta(weeks=int(val['COUNT'])-1)
	return Recurrence(days, time, begin, end)

def parse_event(evt, recurmap):
	sesn = Session()
	sesn.name = evt['summary']
	sesn.gcalid = evt['id']
	sesn.gcalurl = evt['htmlLink']
	sesn.start = parse_datetime(evt['start'])
	sesn.stop = parse_datetime(evt['end'])
	sesn.room = evt['organizer'].get('displayName')
	sesn.owners = [User(user.get('displayName'), user.get('email'))
					for user in evt.get('attendees', ())]
	sesn.recur = recurmap.get(evt.get('recurringEventId'))
	sesn.title = None
	sesn.file = sesn.name + sesn.start.strftime('_%m%d')
	sesn.sections = set()
	sesn.links = set()
	for line in evt.get('description', '').splitlines():
		key, sep, val = line.partition(':')
		if not sep:
			continue
		key = key.lower()
		if key == 'title':
			sesn.title = val.strip()
		elif key == 'file':
			sesn.file = val.strip()
		elif key in ('section', 'sections'):
			sesn.sections.update(val.split())
		elif key in ('link', 'links'):
			sesn.links.update(val.split())
	return sesn

def format_event(evt):
	pass


##	self.rdata = rdata
#			rdata = {'type': tag}
#			if param:
#				rdata['params'] = dict(itm.split('=',1) for itm in param)

	#def as_gobj(self):
	#	res = []
	#	for tag, items in self.rdata.iteritems():
	#		rule = tag
	#		for param, val in items:
	#			param = ';'.join('%s=%s' % item for item in param.iteritems())
	#			if tag in ('RDATE', 'EXDATE'):
	#				val = ','.join(val)
	#			elif tag in ('RRULE', 'EXRULE'):
	#				val = ';'.join('%s=%s' % item for item in val.iteritems())
	#			res.append()

_parts = {'d4': r'(\d\d\d\d)',
		  'd2': r'(\d\d)',
		  'ms': r'(\.\d\d\d)',
		  'tz': r'(Z|[+-](\d\d):(\d\d))'}
date_re = re.compile(r'{d4}-{d2}-{d2}'.format(**_parts))
dtime_re = re.compile(r'{d4}-{d2}-{d2}T{d2}:{d2}:{d2}{ms}?{tz}?'.format(**_parts))

def parse_datetime(value):
	if value is None:
		return None
	elif isinstance(value, basestring):
		tzone = None
		hastime = value[10:11] == 'T'
	elif 'dateTime' in value:
		tzone = value.get('timeZone')
		value = value['dateTime']
		hastime = True
	elif 'date' in value:
		tzone = value.get('timeZone')
		value = value['date']
		hastime = False
	else:
		raise ValueError('unknown date type')
	if hastime:
#		value = datetime(*(int(x) for x in dtime_re.match(value).group(1,2,3,4,5,6)))
		yr,mo,dy,hr,mi,se,ms,tz,oh,om = dtime_re.match(value).groups()
		value = datetime(int(yr), int(mo), int(dy),
						 int(hr), int(mi), int(se)) #, int(ms[1:])*1000 if ms else 0)
		if tzone:
			value = pytz.timezone(tzone).localize(value)
		elif tz == 'Z':
			value = pytz.UTC.localize(value)
		elif tz:
			offset = int(tz[0] + '1') * (int(oh)*60 + int(om))
			value = pytz.FixedOffset(offset).localize(value)
	else:
#		value = datetime(*(int(x) for x in date_re.match(value).group(1,2,3)))
		yr, mo, dy = date_re.match(value).groups()
		value = date(int(yr), int(mo), int(dy))
	return value

def format_datetime(value, as_obj=False):
	if isinstance(value, datetime):
		if value.tzname() in ('UTC', 'UCT'):
			zone = None
			value = value.replace(tzinfo=None).isoformat() + 'Z'
		else:
			zone = getattr(value.tzinfo, 'zone', None)
			value = value.isoformat()
		if as_obj:
			value = {'dateTime': value}
			if zone:
				value['timeZone'] = zone
	else:
		value = value.isoformat()
		if as_obj:
			value = {'date': value}
	return value

